<?php

//set up hook
$wgExtensionFunctions[] = "wfAgonyPermissions";

function wfAgonyPermissions()
{
	global $wgHooks;

	// use the userCan hook to check permissions
	$wgHooks[ 'userCan' ][] = 'agonyCheckPermissions';
	$wgHooks[ 'CategoryPageView' ][] = 'agonyCategoryPageView';
	$wgHooks[ 'EditFilter' ][] = 'agonyEditFilter';
}

function agonyCheckPermissions( $title, $user, $action, $result )
{
	global $wgGroupPermissions, $wgExclusiveNamespaces, $wgSpecialPageAccess;
	
	$text = $title->getPrefixedText();
	$page_exists = $title->exists();
	$id = $title->getArticleID();
	wfDebug("agonyCheckPermissions: Entered (title='{$text}, {$id}' action='{$action}' pageexists='{$page_exists}')\n");
	
	//don't impose extra restrictions on UI pages
	if ($title->isCssJsSubpage()) return true;
	
	// Handle special pages seperate
	$ns = $title->getNamespace();
	if( NS_SPECIAL == $ns )
	{
		if ($action != 'read')
		{
			wfDebug("agonyCheckPermissions: Granted due to action not being 'read'\n");
			$result = NULL;
			return true;
		}
		else if (isSpecialAllowed($title->getText(), $user) == true)
		{
			wfDebug("agonyCheckPermissions: Granted special page access\n");
			$result = true;
			return true;
		}
		else
		{
			wfDebug("agonyCheckPermissions: Denied special page access\n");
			$result = false;
			return false;
		}
	}
	
	$user_allowed = NULL;
	
	$access_categories = array();
	
//get categories for this page
	$parentCategories = $title->getParentCategories();

	//scan list of categories, if any and find those that are access control categories
	if(is_array($parentCategories))
	{
		foreach( $parentCategories as $category => $value)
		{
			wfDebug("agonyCheckPermissions: Found Category: {$category}\n");
			if (isAccessControlCategory($category))
				$access_categories[$category] = false; // Assign initial granted value of false.
		}
	}
	
// If the page has assigned access control categories then process them
	if (count($access_categories) > 0)
	{
		wfDebug("agonyCheckPermissions: Page has access control categories\n");
		// Mark true any category for which the user is allowed the action
		foreach( $access_categories as $category => $value)
		{
			$rights = $user->getRights(); // debug statement
			if ($user->isAllowed("{$category}_{$action}"))
			{
				wfDebug("agonyCheckPermissions: {$category}_{$action} = true\n");
				$access_categories[$category] = true;
			}
		}
		
		$all_false = true;
		$all_true = true;
		
		// Check global category permission first
		if( $user->isAllowed("Category:*_{$action}") )
		{
			wfDebug("agonyCheckPermissions: Category:*_{$action} = true\n");
			$all_false = false;
		}
		else
		{
			foreach( $access_categories as $category => $value)
			{
				if ($value == true)
					$all_false = false;
				else
					$all_true = false;
			}
		}

		wfDebug("agonyCheckPermissions: All False = {$all_false}  All True = {$all_true}\n");
		
		if ($action == 'read')
		{
			// User can read if he/she has read permission for ANY of the access control categories.
			if ($all_false == false)
				$user_allowed = true;
			else
				$user_allowed = false;
		}
		else
		{
			// User can perform $action ONLY if he/she has permission for ALL of the access control categories.
			if ($all_true == true)
				$user_allowed = true;
			else
				$user_allowed = false;
		}
	}
	
	if (isset($user_allowed))
		wfDebug("agonyCheckPermissions: Based on access control categories user_allowed = {$user_allowed}\n");
	
	$ns = $title->getNamespace();
	if ($user_allowed != true || isset($wgExclusiveNamespaces[$ns]))
	{
		wfDebug("agonyCheckPermissions: Checking Namespace permissions\n");
		if ($user->isAllowed("Namespace:{$ns}_{$action}") || $user->isAllowed("Namespace:*_{$action}"))
		{
			// If a page has been categorized then don't overide permision decision based on those categories
			if (isset($user_allowed) == false)
				$user_allowed = true;
		}
		else
		{
			// Only override category grants if this is an exclusive namespace
			if (isset($wgExclusiveNamespaces[$ns]))
				$user_allowed = false;
		}
	}
		
// Only check base permissisions if there where no category or namespace permissions
	// Limit base checks to non-edit permissions (i.e. sysop type stuff)
	if (in_array($action, array('read','edit','move','create','createpage','createtalk','delete','talk')) == false)
	{
		if (isset($user_allowed) == false && $user->isAllowed($action))
		{
			wfDebug("agonyCheckPermissions: Granted based on base group permissions\n");
			$result = true;
			return true;
		}
	}

	// Allow a user control of their own user page, but only if there are no overriding category or namespace permissions
	if(isset($user_allowed) == false)
	{
		if (isOwnUserPage($title, $user))
			$user_allowed = true;
	}

	// Check to see if user created this page and is the only author
	// Only perform this check if there are no category or namespace permissions specified and there where no base permissions
	if(isset($user_allowed) == false)
	{
		if (isUserSoleAuthor($title, $user))
			$user_allowed = true;
	}

	// If nothing else says yes or not, and the page exists, check the group based permissions
	if(isset($user_allowed) == false && $title->exists() == false)
	{
		if ($user->isAllowed($action))
			$user_allowed = true;
	}

	if(isset($user_allowed) == false && $title->isTalkPage()) // Check permissions of subject page for this talk page
	{
		$talk_action = $action;

		if ($action == 'create')
			$talk_action = 'createtalk';
		else if ($action == 'edit')
			$talk_action = 'talk';

		if (agonyCheckPermissions($title->getSubjectPage(), $user, $talk_action, $result) == true)
			$user_allowed = true;
	}
	else if(isset($user_allowed) == false && $title->isSubpage()) // Check permissions of the parent of this page
	{
		$base_title = Title::newFromText($title->getBaseText(), $title->getNamespace());

		if (agonyCheckPermissions($base_title, $user, $action, $result) == true)
			$user_allowed = true;
	}

	if ($user_allowed != true)
	{
		wfDebug("agonyCheckPermissions: Denied\n");
		$result=false;
		return false;
	}
	else
	{
		wfDebug("agonyCheckPermissions: Granted\n");
		$result = true;
		return true;
	}
}

// Return true of the user is the only author of this article
// A user is the sole author if the first and all subsequent revisions where made by the user.
// This will return false if there are NO revisions for page (i.e. it's a new page)
function isUserSoleAuthor($title, $user)
{
	$db = wfGetDB(DB_SLAVE);
	$rows = $db->select( 'revision', 'distinct rev_user', array('rev_page' => $title->getArticleID()), __METHOD__);
	$has_revisions = false;
	foreach ($rows as $row)
	{
		if ($row->rev_user != $user->getId())
			return false;
		$has_revisions = true;
	}
	return $has_revisions;
}

// return true if this page is the users own User: page.
function isOwnUserPage($title, $user)
{
	if ($title->getNamespace() == NS_USER &&
		$title->getText() == $user->getName())
		return true;
	return false;
}

// Return true if the named category is an access category
function isAccessControlCategory($cat_name)
{
	//global $wgAuth;
	//global $wgAuthDrupal_AccessControlCategoriesTable;
	//global $wgAuthDrupal_TablePrefix;
	
	//$name = getCurrentDrupalUsername();
	//$drupal_user = $wgAuth->getDrupalUser($name);
	//$acct = $drupal_user->makeDrupalTableName($wgAuthDrupal_AccessControlCategoriesTable);
	
	//$categories = $wgAuth->getDrupalAccessControlCategories();
	//	$ret = array_key_exists($cat_name, $categories);
	//	return $ret;
	
	global $wgAccessControlCategories;
	return array_key_exists($cat_name, $wgAccessControlCategories);
}

function isSpecialAllowed($name, $user)
{
	global $wgSpecialPageAccess;

	$groups = $user->getEffectiveGroups();

	$result = NULL;
	foreach ($groups as $group)
	{
		if (isset($wgSpecialPageAccess[$group][$name]))
		{
			wfDebug("isSpecialAllowed: \$wgSpecialPageAccess['{$group}']['{$name}'] = {$wgSpecialPageAccess[$group][$name]}\n");
			if ($result === NULL)
				$result = $wgSpecialPageAccess[$group][$name];
			else if ($wgSpecialPageAccess[$group][$name] == true)
				$result = true;
		}
	}
	
	if ($result === NULL)
	{
		wfDebug("isSpecialAllowed: Checking ['group']['*']\n");
		foreach ($groups as $group)
		{
			if (isset($wgSpecialPageAccess[$group]['*']))
			{
				wfDebug("isSpecialAllowed: \$wgSpecialPageAccess['{$group}']['*'] = {$wgSpecialPageAccess[$group]['*']}\n");
				if ($result === NULL)
					$result = $wgSpecialPageAccess[$group]['*'];
				else if ($wgSpecialPageAccess[$group]['*'] == true)
					$result = true;
			}
		}
	}

	if ($result === NULL && isset($wgSpecialPageAccess['*'][$name]))
	{
		wfDebug("isSpecialAllowed: \$wgSpecialPageAccess['*']['{$name}'] = {$wgSpecialPageAccess['*'][$name]}\n");
		return $wgSpecialPageAccess['*'][$name];
	}

	if ($result === NULL)
		$result = false;
		
	return $result;
}

function agonyCategoryPageView(&$categoryArticle)
{
	global $wgUser;

	$cat_name = $categoryArticle->getTitle()->getPrefixedText();
	if (isAccessControlCategory($cat_name))
	{
		if($wgUser->isAllowed("{$cat_name}_read") || $wgUser->isAllowed("Category:*_read"))
			return true;
		else
			return false;
	}
	else
	{
		return true;
	}
}

function agonyEditFilter($edit_page, $textbox1, $section, &$hookError)
{
	global $wgUser;

	// Get the categories in the unedited version of the page
	$old_categories = array();
	$categories = $edit_page->mTitle->getParentCategories();
	if (is_array($categories))
	{
		foreach ($categories as $category=>$dd)
		{
			$old_categories[] = $category;
		}
	}

	$new_categories = array();

	$category_refs = array();

	// Get the categories in the new potential version
	$num_cats = preg_match_all("/\[\[(Category:[^\]]+)\]\]/", $textbox1, $category_refs, PREG_SET_ORDER);
	if ($num_cats > 0)
	{
		for ($i = 0; $i < $num_cats; $i++)
		{
			$new_categories[] = $category_refs[$i][1];
		}
	}

	$change_cats = array();
	
	// Find which categories where removed (if any)
	foreach ($old_categories as $category)
	{
		if (!in_array($new_categories))
			$change_cats[] = $category;
	}
	
	// Find which categories where added (if any)
	foreach ($new_categories as $category)
	{
		if (!in_array($category, $old_categories))
			$change_cats[] = $category;
	}

	// If the access control categories have changed, then do additional checks
	if (count($change_cats) > 0)
	{
		// User must have the create permission for ALL access control categories.
		// First check for global category create permission.
		if ($wgUser->isAllowed("Category:*_create") != true)
		{
			$bad_cats = array();
			// Make sure the user has the "create" permission for each added/removed category
			foreach ($change_cats as $category)
			{
				if (isAccessControlCategory($category))
				{
					if (!$wgUser->isAllowed("{$category}_create"))
					{
						$bad_cats[] = $category;
					}
				}
			}
			if (count($bad_cats) > 0)
				$hookError = "You do not have permission to add or remove pages from category(s): " . implode(', ', $bad_cats);
		}
	}
	return true;
}